//==================================================================
//  Copyright (C) 2006  Davide Pasca
//
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public
//  License as published by the Free Software Foundation; either
//  version 2.1 of the License, or (at your option) any later version.
//
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//==================================================================
///
///
///
///
///
//==================================================================

#include "stdafx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <float.h>

//==================================================================
namespace M5D
{

//==================================================================
//using namespace M5D;

//==================================================================
Mesh::Mesh( Scene *scenep, const char *namep, Mesh *copy_fromp, CopyParams *copy_paramsp ) :
	Model( Model::TYPE_MESH, scenep, namep ),
	_geomp(NULL),
	_is_own_geom(true),
	_intflags(0)
{
	if ( copy_fromp )
	{
		CopyParams def_copy_params;
		if NOT( copy_paramsp )
			copy_paramsp = &def_copy_params;

		_mod_local_m = copy_fromp->_mod_local_m;
		_mod_global_m = copy_fromp->_mod_global_m;

		if ( copy_paramsp->_copy_geometry )
		{
			CreateGeometry();
			MeshGeometry	*geomp = _geomp;
			MeshGeometry	*src_geomp = copy_fromp->_geomp;

			geomp->_box_min = src_geomp->_box_min;
			geomp->_box_max = src_geomp->_box_max;

			if ( copy_paramsp->_copy_geometry_idx_dlist )
				geomp->_displist_idxp = _mod_scenep->NewDispListCopy( src_geomp->_displist_idxp );

			if ( copy_paramsp->_copy_geometry_vtx_dlist )
				geomp->_displist_vtxp = _mod_scenep->NewDispListCopy( src_geomp->_displist_vtxp );

			if ( copy_paramsp->_copy_geometry_verts )
				geomp->GetVerts().CopyFrom( copy_fromp->GetVerts() );

			if ( copy_paramsp->_copy_geometry_trigs )
			{
				// $$$ should make a CopyFrom() function instead
				geomp->GetTrigs().LockW( __FILE__, __LINE__ );
				geomp->GetTrigs().CopyFrom( src_geomp->GetTrigs() );
				geomp->GetTrigs().Unlock();
			}
			
			// creates a vertex list form the source meshe's geometry
			if ( copy_paramsp->_copy_geometry_create_vtx_dlist )
			{
				geomp->UpdateVtxDispList( _mod_scenep, src_geomp, 
					copy_paramsp->_copy_geometry_create_vtx_dlist_forcetypes );
			}
		}
		else
		{
			SetGeometry( copy_fromp->_geomp );
		}
	}
	else
		CreateGeometry();	

	if ( namep == NULL && copy_fromp != NULL )
	{
		char	buff[64];

		strcpy_s( buff, copy_fromp->GetName() );
		//_tcscat_s( buff, "'" );
		SetName( buff );
	}
}

//==================================================================
void Mesh::markDispListUsedMaterials( DispList *displistp, u_int mark_flags )
{
	int	n = displistp->GetPrimPoolsN();
	for (int i=0; i < n; ++i)
	{
		DispList::Pool	*poolp = displistp->GetPrimPool( i );
		poolp->GetMaterial()->_flags |= mark_flags;
	}
}

//==================================================================
void Mesh::markTrigsUsedMaterials( u_int mark_flags )
{
	Triangles	&trigs = _geomp->GetTrigs();
	trigs.LockRW( __FILE__, __LINE__ );

	for (M5D::triangle_coiter tp = trigs._list.begin(); tp != trigs._list.end(); ++tp)
	{
		if ( tp->materialp )
			tp->materialp->_flags |= mark_flags;
	}

	trigs.Unlock();
}

//==================================================================
void Mesh::markMaterials( u_int mark_flags )
{
	if ( IsOwnedGeometry() )
	{
		DispList	*dlidxp = OpenDispListIndex();
		DispList	*dlvtxp = OpenDispListVertex();

		if ( dlidxp || dlvtxp )
		{
			if ( dlidxp )
				markDispListUsedMaterials( dlidxp, mark_flags );

			if ( dlvtxp )
				markDispListUsedMaterials( dlvtxp, mark_flags );
		}
		else
		{
			markTrigsUsedMaterials( mark_flags );
		}

		CloseDispListIndex();
		CloseDispListVertex();
	}
}

//==================================================================
Mesh::~Mesh()
{
	if ( _is_own_geom )
		SAFE_DELETE( _geomp );

	// only hint material release if our geometry isn't instantiated
	if ( IsOwnedGeometry() )
	{
		_mod_scenep->HintFreedMaterials();
	}
}

//==================================================================
void MeshGeometry::UpdateIdxDispList( Scene *scenep )
{
	if ( !_displist_idxp_verts_syn.needs_update( _verts._revcnt ) &&
		 !_displist_idxp_trigs_syn.needs_update( _trigs._revcnt ) )
		return;

	if ( _verts.GetPosN() == 0 || _trigs._list.size() == 0 )
		return;


	if NOT( _displist_idxp )
		_displist_idxp = scenep->NewDispList();

	_verts.LockR( __FILE__, __LINE__ );

	try {
		_displist_idxp->RebuildIdxDList( _trigs, _trigs._list.size(),
			_verts.GetPosN() ? _verts.GetPosR() : NULL, DListVertPool::FORMAT_FLOAT,
			_verts.GetTexN() ? _verts.GetTexR() : NULL, DListVertPool::FORMAT_FLOAT,
			_verts.GetColN() ? _verts.GetColR() : NULL, DListVertPool::FORMAT_BYTE,
			_verts.GetNorN() ? _verts.GetNorR() : NULL, DListVertPool::FORMAT_FLOAT );
	} catch (...) {
		
		_verts.Unlock();
		SAFE_DELETE( _displist_idxp );
	}

	_verts.Unlock();
}

//==================================================================
void MeshGeometry::UpdateVtxDispList( Scene *scenep, MeshGeometry *src_geomp, const bool *foced_vtypesp )
{
	/* WTF !? Updating Vertex List makes no sense currently.. it's for when there is no primitive list, I guess
	if NOT( src_geomp )
		src_geomp = this;

	const MultiVerts	&src_verts = src_geomp->_verts;
	Triangles			&src_trigs = src_geomp->_trigs;

	if ( !_displist_vtxp_verts_syn.needs_update( src_verts._revcnt ) &&
		 !_displist_vtxp_trigs_syn.needs_update( src_trigs._revcnt ) )
		return;

	if ( src_verts._pos_vertsp.len() == 0 || src_trigs._list.size() == 0 )
		return;

	if NOT( _displist_vtxp )
		_displist_vtxp = scenep->NewDispList();

	try {
		_displist_vtxp->RebuildVtxDList( src_trigs, src_trigs._list.size(),
			&src_verts._pos_vertsp.front(), DListVertPool::FORMAT_FLOAT,
			&src_verts._tex_vertsp.front(), DListVertPool::FORMAT_FLOAT,
			&src_verts._col_vertsp.front(), DListVertPool::FORMAT_BYTE,
			&src_verts._nor_vertsp.front(), DListVertPool::FORMAT_FLOAT,
			foced_vtypesp );
	} catch (...) {
		SAFE_DELETE( _displist_vtxp );
	}
	*/
}

//==================================================================
void Mesh::SetGeometry( MeshGeometry *geomp )
{
	PASSERT( _geomp == NULL );

	if ( _is_own_geom && _geomp )
		delete _geomp;

	_geomp = geomp;
	_is_own_geom = false;
}

//==================================================================
void Mesh::CreateGeometry()
{
	PASSERT( _geomp == NULL );

	_geomp = new MeshGeometry();
	_is_own_geom = true;
}

//==================================================================
void Mesh::UpdateGeometry()
{
	CalcBoundingBox();
	calcTrigsNormals();
}
/*
//==================================================================
triangle_t *Mesh::AddTrigs( int add_n )
{
	_geomp->GetTrigs().AssertLockW();
	return _geomp->GetTrigs()._list.expand( add_n );
}
*/
//==================================================================
Triangle *Mesh::msh_trig_addcopy( const Triangle *src_trip )
{
	Triangle *new_trip = _geomp->GetTrigs().AddTrigsDirty( 1 );

	if ( new_trip )
		*new_trip = *src_trip;

	return new_trip;
}

//==================================================================
// should invalidate stuff such as the work area
/*
void Mesh::mev_verts_changed( Mesh *gp )
{
}
*/

//==================================================================
void Mesh::CalcBoundingBox()
{
	MultiVerts	&verts = _geomp->GetVerts();

	Vector3	*vp = &_geomp->GetVerts()._pos_vertsp.front();
	Vector3	*vpend = &_geomp->GetVerts()._pos_vertsp.back() + 1;

	Vector3	&box_min = _geomp->_box_min;
	Vector3	&box_max = _geomp->_box_max;

	box_min.SetMaxValue();
	box_max.SetMinValue();
	for (; vp != vpend; ++vp)
	{
		box_min.SetMin( *vp );
		box_max.SetMax( *vp );
	}
}

//==================================================================
void Mesh::TransformGeometry( const Matrix44 &trmat, const Matrix44 *normal_mp )
{
	_geomp->_box_min = trmat * _geomp->_box_min;
	_geomp->_box_max = trmat * _geomp->_box_max;

	_geomp->GetVerts().LockRW( __FILE__, __LINE__ );

	Vector3	*posp = _geomp->GetVerts().GetPosW();
	for (int j=_geomp->GetVerts().GetPosN(); j; --j, ++posp)
		*posp = trmat * *posp;

	if ( normal_mp )
	{
		Vector3	*norp = _geomp->GetVerts().GetNorW();

		for (int j=_geomp->GetVerts().GetNorN(); j; --j, ++norp)
			*norp = *normal_mp * *norp;
	}

	_geomp->GetVerts().Unlock();
}

//==================================================================
void Mesh::CleanupGeometry()
{
	Triangles	&trigs = _geomp->GetTrigs();

	// first lock only on read
	trigs.LockR( __FILE__, __LINE__ );

	triangle_iter	it = trigs._list.begin();
	for (; it != trigs._list.end(); ++it)
	{
		// if a degenerate triangle is found, then lock on write from now on
		if ( it->IsDegenerate() )
		{
			trigs.Unlock();
			trigs.LockW( __FILE__, __LINE__ );

			for (; it != trigs._list.end(); ++it)
			{
				if ( it->IsDegenerate() )
				{
					it->Invalidate();
				}
			}

			trigs.Unlock();

			eliminateBadTrigs();
			killUnusedPosVerts();
			return;
		}
	}

	trigs.Unlock();
}

//==================================================================
void Mesh::ScaleAnim( float sx, float sy, float sz )
{
/*
	for (int j=0; j < _tnodes_n; ++j)
	{
	m5d_tnode_c	*bp;

		bp = _tnodesp + j;
		for (int k=0; k < bp->_n_poskeys; ++k)
		{
			bp->_poskeysp[k].pos_vec[0] *= sx;
			bp->_poskeysp[k].pos_vec[1] *= sy;
			bp->_poskeysp[k].pos_vec[2] *= sz;
		}

		// $$$
		bp->_bone_vert_m[3][0] *= sx;
		bp->_bone_vert_m[3][1] *= sy;
		bp->_bone_vert_m[3][2] *= sz;

		bp->_base_poskey.pos_vec[0] *= sx;
		bp->_base_poskey.pos_vec[1] *= sy;
		bp->_base_poskey.pos_vec[2] *= sz;
	}

	_lw_mat[3][0] *= sx;
	_lw_mat[3][1] *= sy;
	_lw_mat[3][2] *= sz;
*/
}

};